package org.knot.mockejb3;


import javax.naming.Context;
import javax.naming.NamingException;

import org.knot.mockejb3.interceptor.AspectSystem;
import org.knot.mockejb3.interceptor.AspectSystemFactory;


/**
 * Provides methods to "deploy" EJBs. Most deploy  
 * methods simply create Home proxy and bind it to the JNDI. 
 * Since MockEjb is not a container in a true sense, the purpose of this class
 * is to provide an abstraction that emulates EJB deployment.
 * 
 */
public class MockContainer {
    
    
    private Context context;
    
    /**
     * We store the security context (principal) on a thread. The field is static to make sure that 
     * there is only one per thread
     */
    private static ThreadLocal threadContext = new ThreadLocal(); 
    

    /**
     * Creates a new instance of the MockContainer for 
     * the given context, deletes all aspects from the AspectSystem and adds the system interceptors, such as 
     * ExceptionHandler.
     * Clears the EntityDatabase as well.
     * Creates a default (anonymous) user which is used for all EJB operations unless "login"
     * is called explicitly.  
     * @param context JNDI context to use for all "bind" operations
     */
    public MockContainer( final Context context ){
        this.context = context;
        setupDefaultInterceptors();
    }
    
    /**
     * Add the interceptors that should always be present to 
     * the AspectSystem.
     */
    protected void setupDefaultInterceptors(){
        
        AspectSystem aspectSystem = AspectSystemFactory.getAspectSystem();
        aspectSystem.clear();
        
        // All beans should have the exception handler
        aspectSystem.addFirst( new EjbExceptionHandler());
    }

    
    /**
     * Deploys session bean specified by the given descriptor.
     * <code>MockContainer</code> creates the proxy implementing 
     * session bean home interface and binds it to the JNDI context
     * using <code>rebind()</code> method. 
     * Clients can subsequently lookup the home and invoke <code>create()</code>.  
     * @param descriptor descriptor of the session bean to deploy 
     * As of MockEJB 0.6, this method does not return MockEjbObject since  
     * the direct use of MockEjbObject is deprecated. AspectSystem should be used instead.
     */
    public void deploy( SessionBeanDescriptor descriptor ) throws NamingException {
        
        MockEjbObject ejbObject = new MockEjbObject( descriptor.getIfaceClass() );
        
        Object bean = createBean(descriptor);
        
        context.rebind( descriptor.getJndiName(), ejbObject.createProxy( bean) );
    }
    
    protected Object createBean(BasicEjbDescriptor descriptor)
    {
        Object bean;
        if(descriptor.getBean() == null)
            bean = invokeNewInstance(descriptor.getBeanClass());
        else
            bean = descriptor.getBean();
        return bean;
    }
    
    protected Object invokeNewInstance(Class beanClass)
    {
        Object bean = null;
        try
        {
            bean = beanClass.newInstance();
        }
        catch(IllegalAccessException iae)
        {
            throw new MockEjbSystemException("Error instantiating a bean " + beanClass.getName(), iae);
        }
        catch(InstantiationException ie)
        {
            throw new MockEjbSystemException("Error instantiating a bean " + beanClass.getName(), ie);
        }
        return bean;
    }
    
    
    /**
     * Tests if the given throwable is the system exception in terms of the container.
     * Currently we consider all runtime exceptions and transaction-related exceptions
     * system exceptions.
     * <br>Note that the spec is vague on 
     * what is system and non-system exception, so this method might change in the future.
     * 
     * @param throwable exception in question
     * @return true if the given throwable is "system" exception
     */
    // TODO: provide the facility for setting an array of exception types.
    public static boolean isSystemException( Throwable throwable ){
    
        return ( throwable instanceof RuntimeException ||
            throwable instanceof java.rmi.RemoteException ||
            throwable instanceof javax.transaction.SystemException ||
            throwable instanceof javax.transaction.NotSupportedException ||             
            throwable instanceof javax.transaction.InvalidTransactionException ||             
            throwable instanceof java.lang.reflect.InvocationTargetException);       
    }

}
